Param([string]$SiteUrl,[switch]$Verbose)
Add-PSSnapin Microsoft.SharePoint.PowerShell

$location = Get-Location
$global:logFolder = $location.ProviderPath + "\Logs";
$global:backupFolder = $location.ProviderPath +"\Backup";

$now = (Date);

$global:verbose = $Verbose;
#File paths
$global:backupFilePath = $location.ProviderPath + "\Permissions.xml"
$global:logFilePath = $global:logFolder + "\" + $now.ToString("yyyy-MM-dd hh-mm-ss tt") + "-backup.txt";

#Backup Configuration
$global:includeFilePermission = $true;

#Document Library Filter
$global:excludeCatalog = $true;
$global:excludePrivate = $true;
$global:excludeHidden = $true;

#check and create Log Folder
$logFolderExist = Test-Path $global:logFolder;
if($logFolderExist-eq $false)
{
	New-Item -ItemType directory -Path $global:logFolder | Out-Null
}

#check and create Backup Folder
$logFolderExist = Test-Path $global:backupFolder;
if($logFolderExist-eq $false)
{
	New-Item -ItemType directory -Path $global:backupFolder | Out-Null
}


$global:stream = "";

####################
#                  #
####################
function Log
{
	Param($msg)
	$now = Date
	$output = "Info:`t" + $now.ToString("yyyy-MM-dd hh:mm:ss tt") + ": ";
	for($i = 0; $i -lt $indent; $i++)
	{
		$output += "`t"
	}
	$output += $msg;
	$output | Out-File $global:logFilePath -Append -Force
	Write-Host $output;
}

####################
#                  #
####################
function Error
{
	Param($msg)
	$now = Date
	$output = "Error:`t" + $now.ToString("yyyy-MM-dd hh:mm:ss tt") + ": " + $msg;
	$output | Out-File $global:logFilePath -Append -Force
	Write-Host -f Red $output;
}

####################
#                  #
####################
function StreamWrite 
{
	Param(
		$value,
		$indent
	)
	
	if($indent -eq $null)
	{
		$indent = 0;
	}
	
	$output = "";
	#doing proper indentation
	for($i = 0; $i -lt $indent; $i++)
	{
		$output += "`t"
	}
	$output += $value
	$global:stream.WriteLine($output);
}

####################
#                  #
####################
function UpdatePermission
{
	Param(
    [Microsoft.SharePoint.SPWeb]$spWeb, 
    $spList, 
    $spListItem, 
    $itemXml
	)
	
	if($itemXml.RoleAssignments)
	{
		Write-Host -f Yellow "Breaking permission..."
		$spListItem.BreakRoleInheritance($false);
		$itemXml.RoleAssignments.RoleAssignment | ForEach-Object{
			$roleAssignment = $null;
			if($_.Group)
			{
				$groupName = $_.Group;
				$groupName = ReplaceItemDynamicValue $spWeb $spList $groupName
				$spGroup = $spWeb.SiteGroups[$groupName];
                if($spGroup)
				{	
					Write-Host "`t$groupName"
					$roleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($spGroup)
				}
				else
				{
					Write-Host -f Yellow "`tCannot find group $groupName"
				}				
				
			}
			
			if($_.User)
			{
				$login = $_.User;
				$spUser = $spWeb.EnsureUser($login);
				if($spUser)
				{
					Write-Host "`t$login";
					$roleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($spUser)
				}
				else
				{
					Write-Host -f Yellow "`tCannot find User $login";
				}
			}
			
			
			$_.RoleDefinitionBindings.RoleDefinition | ForEach-Object {
				$typeName = $_.Name;
				$roleDefinition = $spWeb.RoleDefinitions[$typeName];
                if($roleDefinition)
                {
				    Write-Host "`t`t$typeName"
				    $roleAssignment.RoleDefinitionBindings.Add($roleDefinition);
                }
                else
                {
                    Write-Host -f Yellow "Unable to find Role Definition '$typeName'";
                }
			}
			
			
			$spListItem.RoleAssignments.Add($roleAssignment)
		}
		$spListItem.Update();
	}
}

####################
#                  #
####################
function BackupPermission
{
	Param(
		[Microsoft.SharePoint.SPRoleAssignmentCollection]$col,
		$indent
	)
	StreamWrite "<RoleAssignments>" $indent;
	$col | ForEach-Object {
		$roleAssignment = $_;
		$member = [Microsoft.SharePoint.SPPrincipal]$roleAssignment.Member;
		$isGroup = $member -is [Microsoft.SharePoint.SPGroup]
		$roleBindings = $roleAssignment.RoleDefinitionBindings;
		
		
		$containsPermissionOtherThanLimitedAccess = $null;
		$roleBindings | ForEach-Object {
			$name = $_.Name
			if($name -ne "Limited Access")
			{
				$containsPermissionOtherThanLimitedAccess = $true;
			}
		}
			
		if($containsPermissionOtherThanLimitedAccess)
		{
			if($isGroup)
			{
				StreamWrite "<RoleAssignment Group=""$($member.Name)"">" ($indent+1);
			}
			else
			{
				StreamWrite "<RoleAssignment User=""$($member.UserLogin)"">" ($indent+1);
			}
			StreamWrite "<RoleDefinitionBindings>" ($indent+2);
			$roleBindings | ForEach-Object {
				$name = $_.Name
				if($name -ne "Limited Access")
				{
					StreamWrite "<RoleDefinition Name=""$name""/>" ($indent+3);
				}
			}
			StreamWrite "</RoleDefinitionBindings>" ($indent+2);
			StreamWrite "</RoleAssignment>" ($indent+1);
			
		}
		else
		{
			#skip backing up Limited Access Permission Only.
			if($global:verbose)
			{
				Log "Contains only Limited Access.. Skipping this item..."
			}
		}
	}
	StreamWrite "</RoleAssignments>" $indent;
}


####################
#                  #
####################
function BackupWebPermission
{
	Param(
		[Microsoft.SharePoint.SPWeb]$web,
		$indent,
		$isRootWeb = $false
	)
	$tagName = "Web"
	if($isRootWeb)
	{
		$tagName = "RootWeb"
	}
	
	###############################################################
	#backup web permission
	Log ("Backing up web " + $web.Url);
	StreamWrite "<$tagName>" $indent
	StreamWrite ("<Title>" + $web.Title + "</Title>") ($indent+1)
	StreamWrite ("<Url>" + $web.Url + "</Url>") ($indent+1)
	if($web.HasUniqueRoleAssignments -eq $false)
	{
		if($global:verbose)
		{
			Log "`tWeb inheriting parent permission."
		}
	}
	else
	{
		if($global:verbose)
		{
			Log "`tSaving unique permission."
		}
		BackupPermission $web.RoleAssignments ($indent+1);
	}
	
	################################################################
	#backup document library permission base on global doc library filter condition
	$docLibraries = $web.Lists | ? { $_ -is [Microsoft.SharePoint.SPDocumentLibrary]}
	if($global:excludeHidden)
	{
		$docLibraries = $docLibraries | ? {$_.Hidden -eq $false}
	}
	if($global:excludeCatalog)
	{
		$docLibraries = $docLibraries | ? {$_.IsCatalog -eq $false}
	}
	if($global:excludePrivate)
	{
		$docLibraries = $docLibraries | ? {$_.IsPrivate -eq $false}
	}
	
	Log("`tBacking up Document Libraries...");
	StreamWrite "<Lists>" ($indent + 1)
	$docLibraries | ForEach-Object{
		$docLib  = $_;
		BackupDocumentLibraryPermission $docLib ($indent + 2)
	}
	StreamWrite "</Lists>" ($indent + 1)
	
	
	##################################################################
	#backup subweb permission (if any)
	$subWebs = $web.Webs;
	StreamWrite "<Webs>" ($indent+1)
	$subWebs | ForEach-Object {
		$subWeb = $_;
		BackupWebPermission $subWeb ($indent +2)
	}
	StreamWrite "</Webs>" ($indent+1)
	StreamWrite "</$tagName>" $indent
}

####################
#                  #
####################
function BackupDocumentLibraryPermission
{
	Param(
		[Microsoft.SharePoint.SPDocumentLibrary]$docLib,
		$indent
	)
	
	#backup document library permission
	Log("`tDocument Library ""$($docLib.Title)""");
	StreamWrite "<List>" $indent
	StreamWrite ("<Title>" + $docLib.Title + "</Title>") ($indent+1)
	if($docLib.HasUniqueRoleAssignments -eq $false)
	{
		if($global:verbose)
		{
			Log "`t`tInherit parent permission."
		}
	}
	else
	{
		if($global:verbose)
		{
			Log "`t`tSaving unique Permission"
		}
		BackupPermission $docLib.RoleAssignments ($indent+1);
	}
	
	#backup folders permission
	$rootFolder = $docLib.RootFolder
	if($rootFolder.SubFolders.Count -gt 1)
	{
		BackupFolderPermission $rootFolder ($indent + 1) $true;
	}
	else
	{
		if($global:verbose)
		{
			Log ("`t`tLibrary does not contain any folder")
		}
	}
	StreamWrite "</List>" $indent
}


####################
#                  #
####################
function BackupFolderPermission
{
	Param(
		[Microsoft.SharePoint.SPFolder]$folder,
		$indent,
		$isRootFolder = $false
	)
	
	$tagName = "Folder";
	if($isRootFolder)
	{
		$tagName = "RootFolder";
	}
	
	#backup folder permission
	Log("`t`tFolder ""$($folder.Name)""");
	if($folder.Item -ne $null -or $isRootFolder)
	{
		$folderItem = $folder.Item;
		StreamWrite "<$tagName>" $indent
		StreamWrite ("<Name>" + $folder.Name + "</Name>") ($indent+1)
		StreamWrite ("<Url>" + $folder.Url + "</Url>") ($indent+1)
		
		if($folderItem.HasUniqueRoleAssignments -eq $false)
		{
			if($global:verbose)
			{
				Log "`t`t`tInherit parent permission."
			}
		}
		else
		{
			#Only backup permission for non root folder.
			if($isRootFolder -eq $false)
			{
				if($global:verbose)
				{
					Log "`t`t`tSaving unique permission."
				}
				BackupPermission $folderItem.RoleAssignments ($indent+1);
			}
		}
		
		#backup sub folders permission
		$subFolders = $folder.SubFolders;
		if($subFolders.Count -gt 1)
		{
			StreamWrite "<SubFolders>" ($indent+1)
			$subFolders | ForEach-Object {
				$subFolder = $_;
				BackupFolderPermission $subFolder ($indent + 2);
			}
			StreamWrite "</SubFolders>" ($indent+1)
		}
		
		if($global:includeFilePermission)
		{
			
			#backup files permission
			$files = $folder.Files;
			if($files.Count -gt 0)
			{
				Log "`t`t`tChecking $($files.Count) files..."
				StreamWrite "<Files>" ($indent+1)
				$files | ForEach-Object {
					$file = $_;
					BackupFilePermission $file ($indent + 2);
				}
				StreamWrite "</Files>" ($indent+1)
			}
		}
		
		StreamWrite "</$tagName>" $indent
	}
	else
	{
		if($global:verbose)
		{
			Log "`t`t`tNot a valid Folder"
		}
	}
}


####################
#                  #
####################
function BackupFilePermission
{
	Param(
		[Microsoft.SharePoint.SPFile]$file,
		$indent
	)
	
	$fileName = $file.Name;
	$fileItem = $file.Item;
	
	if($global:verbose)
	{
		Log("`t`tFile ""$fileName""");
	}
	
	if($file.Item -ne $null)
	{
		if($fileItem.HasUniqueRoleAssignments -eq $false)
		{	
			if($global:verbose)
			{
				Log "`t`t`tInherit parent permission."
			}
		}
		else
		{
			StreamWrite "<File>" $indent;
			StreamWrite "<Name>$($file.Name)</Name>" ($indent+1);
			StreamWrite "<Url>$($file.Url)</Url>" ($indent+1);
			BackupPermission $fileItem.RoleAssignments ($indent+1);
			StreamWrite "</File>" $indent;
		}
	}
	else
	{
		if($global:verbose)
		{
			Log "`t`t`tNot a valid File"
		}
	}
	
}

$sites;
if($SiteUrl -eq "")
{
	$sites = Get-SPSite
}
else
{
	$sites = Get-SPSite $SiteUrl;
}

Log "=== Starting Scripting ===";

Log "Document Library Filter"
Log "`tExcludeHidden:$($global:excludeHidden)";
Log "`tExcludePrivate:$($global:excludePrivate)";
Log "`tExcludeCatalog:$($global:excludeCatalog)";
Try
{
	$global:stream = [System.IO.StreamWriter] $backupFilePath
	#$stream.AutoFlush = $true;
	StreamWrite "<?xml version=""1.0"" encoding=""UTF-8""?>"
	StreamWrite "<SharePoint>"
	StreamWrite "<Sites>" 1
	
	$sites | ForEach-Object {
		$site = $_;
		$rootWeb = $site.RootWeb;
		if($rootWeb)
		{
			Log ("Backing up Site " + $rootWeb.Url);
			StreamWrite "<Site>" 2
			StreamWrite ("<Url>" + $site.Url+ "</Url>") 3
			BackupWebPermission $rootWeb 3 $true;
			StreamWrite "</Site>" 2
		}
		else
		{
			Error "Site " + $site.Url + " does not have a Root Web";
		}
	}
	
	StreamWrite "</Sites>" 1
	StreamWrite "</SharePoint>"
	
}
catch [Exception]
{
	Error ("Error generating the file. File may have locked by other process `n" + $_.Exception)
}
finally
{
	$global:stream.Close();
	
	#back up the backup permission file.
	$newBackupFilePath = $global:backupFolder + "\Permissions-" + $now.ToString("yyyy-MM-dd hh-mm-ss tt") + ".xml";
	Copy-Item $global:backupFilePath $newBackupFilePath
}

Log "=== Script Completed ==="

